#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

//2. 数组指针变量
//2.1 数组指针变量是什么？
//字符指针是指向字符的指针，整型指针是指向整型的指针。那么数组指针就是指向数组的指针。
//和指针数组区分一下：指针数组是存放指针的数组
//&arr:是数组的地址
//arr：是数组首元素的地址
//数组指针变量是能够指向数组的指针变量
//int* p1[10];//这是存放指针的数组 -- 指针数组(方括号的优先级比*高，所以p1优先和方括号结合成为数组,数组里面存放int*类型的变量)
//int(*p2)[10];//p2是指针变量，指向的是数组 -- 数组指针（p2和*结合，是指针类型）


//2.2 数组指针变量怎么初始化？
//int main()
//{
//	int arr[10] = {0};
//
//	int* p1 = arr;//等号左右两边的类型肯定要相同呀
//	//int*    int*   首元素（int）的地址（*）
//	printf("%p\n", p1);
//	printf("%p\n", p1+1);//跳过多少个字节取决于指针指向元素的类型：int类型
//			  
//	int(*p2)[10] = &arr;//去掉名字剩下的就是类型:数组指针类型
//	//int(*)[10]   int(*)[10]   //int代表数组中元素的类型 *代表指针 [10]代表数组中元素个数
//								//int(*)[10] -- 指向10有个int类型元素数组的指针
//	printf("%p\n", p2);
//	printf("%p\n", p2+1);//跳过多少个字节取决于指针指向元素的类型：int [10]类型 跳过去四十个字节(跳过整个数组)
//						 //这个是野指针，但是泥只要补趣用它⑨补危险
//	return 0;
//}


//3. ⼆维数组传参的本质
//二维数组的数组名也是数组首元素的地址，那么整个首元素到底是神魔？
//是第一行吗？是第一行第一列的元素吗？
//二维数组的本质就是n个一位数组，二维数组里的元素就是一维数组
//相当于:arr[3]={arr1,arr2,arr3};
//那么，这个首元素的地址就是arr1整个数组的地址，也就是第一行的地址,类型是数组指针类型，而不是第一行第一个元素的地址.
//void print(int(*p)[5], int r, int c)//用数组指针来接收，因为传来的是二维数组首元素的地址
//{									//该地址是第0个一维数组整个数组的地址。(可以写成数组的形式也可以写成指针的形式)
//	int i = 0;
//	for (i = 0; i < r; i++)
//	{
//		int j = 0;
//		for (j = 0; j < c; j++)
//		{
//			printf("%d ", *(*(p + i) + j));//相当于p[i][j]
//		}
//		printf("\n");
//	}
//}
//int main()
//{
//	int arr[3][5] =
//	{
//		{1,2,3,4,5},
//		{2,3,4,5,6},
//		{3,4,5,6,7}
//	};
//	print(arr,3,5);//传过去的其实是arr[0]这是整个第一行的地址
//	return 0;
//}
//总结：⼆维数组传参，形参的部分可以写成数组，也可以写成指针形式


//4. 函数指针变量
//根据前⾯学习整型指针，数组指针的时候，我们的类⽐关系，我们不难得出结论：
//函数指针变量应该是⽤来存放函数地址的，未来通过地址能够调⽤函数的。
//那么函数是否有地址呢？
//int Add(int x, int y)
//{
//	return x + y;
//}
//int main()
//{
//	printf("Add =%p\n", Add);
//	printf("&Add=%p\n", &Add);
//	//&函数名和函数名都是函数的地址，没有区别~
//	//函数指针变量的写法其实和数组指针⾮常类似
//	int (*pf)(int, int) = &Add;
//	//类型为: int (*)(int,int)  第一个int代表函数的返回类型，后面两个int代表函数的参数类型，（*）代表指针
//	//这种类型就是函数指针类型,和数组指针的书写十分相似~
//
//	//4.2 函数指针变量的使⽤
//	//通过函数指针调⽤指针指向的函数。
//	int c = (*pf)(3, 5);//解引用+传参(如果没有参数就不用传参直接解引用就可以了)
//						//但是函数书写的时候是Add(参数1，参数2),这个Add是一个地址呀，pf也是这个函数的地址呀
//	int d = pf(5, 6);   //所以就可以不需要解引用
//	printf("c=%d\n", c);
//	printf("d=%d\n", d);
//	return 0;
//}


//4.3 两段有趣的代码
//代码1
//( *( void (*)() ) 0)();
//void (*)() -- 函数指针类型：返回值是void，无参数
//( void (*)() ) -- 强制类型转换
//( void (*)() ) 0 -- 把0强制类型转换为函数指针类型
//这就意味着我们假设0地址处放着无参，返回类型是void的函数
//最终是解引用调用0地址处的这个函数，无参数

//代码2
//void (* signal( int, void(*)(int) ) )(int);
//这是一段函数的声明 -- signal( int, void(*)(int) ) 参数类型为int和函数指针类型
//这个函数的返回值为void(*)(int)
//就相当于: void(*)(int) signal( int, void(*)(int) ) 
//           返回类型     函数名     参数
//意思是这样，但是不允许这么写


//4.3.1 typedef 关键字
//typedef 是⽤来类型重命名的，可以将复杂的类型，简单化。
//⽐如，你觉得 unsigned int 写起来不⽅便，如果能写成 uint 就⽅便多了
//typedef unsigned int uint -- 将unsigned int 重命名为uint

//对于数组指针和函数指针稍微有点区别：
//⽐如我们有数组指针类型 int(*)[5], 需要重命名为 parr_t ，那可以这样写：
//typedef int(*parr_t)[5]; //新的类型名必须在*的右边

//函数指针类型的重命名也是⼀样的，⽐如，将 void(*)(int) 类型重命名为 pf_t ,就可以这样写：
//typedef void(*pfun_t)(int);//新的类型名必须在*的右边

//typedef和define的区别:
//typedef int* ptr_t
//#define PTR_T int*
//ptr_t p1,p2;//p1和p2都是int*类型
//PTR_T p3,p4;//p3是int*类型，p4是int类型
//因为define只起到一个替换的作用，把int*替换为PTR_T
//那么替换前: int *p3,p4;第一个*给p3用了，p4是int类型


//5. 函数指针数组
//顾名思义：存放函数指针类型的数组（开始套娃了）
//int Add(int x, int y)
//{
//	return x + y;
//}
//int Sub(int x, int y)
//{
//	return x - y;
//}
//int Mul(int x, int y)
//{
//	return x * y;
//}
//int Div(int x, int y)
//{
//	return x / y;
//}
//int main()
//{
//	int(*pfarr[4])(int, int) = { Add,Sub,Mul,Div };//类型为:int(*[4])(int,int)是函数指针数组
//												   //存放的元素的类型为: int(*)(int,int)是函数指针,函数名本身就是一个指向函数的指针
//	for (int i = 0; i < 4; i++)
//	{
//		printf("%d ", pfarr[i](8, 4));
//	}
//	return 0;
//}



//6. 转移表
//函数指针数组的⽤途：转移表
//举例：计算器的⼀般实现：（两个整数的运算）

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
//int main()
//{
//	int input = 0;
//	do
//	{
//		printf("1.Add 2.Sub 3.Mul 4.Div 0.退出\n");
//		scanf("%d", &input);
//		int x = 0;
//		int y = 0;
//		int z = 0;
//		switch (input)
//		{
//		case 1:
//			printf("请输入两个操作数:>");
//			scanf("%d %d", &x, &y);
//			z = Add(x, y);
//			printf("结果为:%d\n", z);
//			break;
//		case 2:
//			printf("请输入两个操作数:>");
//			scanf("%d %d", &x, &y);
//			z = Sub(x, y);
//			printf("结果为:%d\n", z);
//			break;
//		case 3:	
//			printf("请输入两个操作数:>");
//			scanf("%d %d", &x, &y);
//			z = Mul(x, y);
//			printf("结果为:%d\n", z);
//			break;
//		case 4:
//			printf("请输入两个操作数:>");
//			scanf("%d %d", &x, &y);
//			z = Div(x, y);
//			printf("结果为:%d\n", z);
//			break;
//		case 0:
//			printf("退出计算器");
//			break;
//		default:
//			printf("输入错误，请重新输入:\n");
//			break;
//		}
//	} while (input);
//	return 0;
//}

//使用函数指针数组的写法:
//int main()
//{
//	int input = 0;
//	do
//	{
//		printf("1.Add 2.Sub 3.Mul 4.Div 0.退出\n");
//		printf("请输入你的选择:>");
//		scanf("%d", &input);
//		if (input >= 1 && input <= 4)
//		{
//			int(*pfArr[])(int, int) = { 0,Add,Sub,Mul,Div };
//			int x = 0;
//			int y = 0;
//			printf("请输入两个操作数:>");
//			scanf("%d %d", &x, &y);
//			printf("结果为： %d\n", pfArr[input](x, y));
//		}
//		else if (input == 0)
//		{
//			printf("退出");
//			break;
//		}
//		else
//		{
//			printf("输入错误，请重新输入:");
//		}
//	} while (input);
//
//	return 0;
//}

//另外一种写法:
//int calc(int(*pf)(int, int))
//{
//	int x = 0;
//	int y = 0;
//	printf("请输入两个操作数:>");
//	scanf("%d %d", &x, &y);
//	printf("结果为:>%d\n", pf(x, y));
//}
//int main()
//{
//	int input = 0;
//	do
//	{
//		printf("1.Add 2.Sub 3.Mul 4.Div 0.退出\n");
//		printf("请选择:>");
//		scanf("%d", &input);
//		switch (input)
//		{
//		case 1:
//			calc(Add);
//			break;
//		case 2:
//			calc(Sub);
//			break;
//		case 3:	
//			calc(Mul);
//			break;
//		case 4:
//			calc(Div);
//			break;
//		case 0:
//			printf("退出计算器");
//			break;
//		default:
//			printf("输入错误，请重新输入:\n");
//			break;
//		}
//	} while (input);
//	return 0;
//}
